#include <limits.h>
#include <sys/time.h>
#include "protocol.h"
#include "storage.h"

/**
 * Initializes the internals of a new buffer structure. Note that it is expected
 * for the memory to already be allocated.
 * 
 * @param new Pointer to the buffer to initialize.
 */

void storage_init(struct buffer* new) {
    new->head = new->tail = 0;
    if (pthread_mutex_init(&new->mutex, NULL))
        perror(NULL);
}

/**
 * Copies the provided tcpd_container into the specified buffer. The buffer must
 * already be initialized. If the sequence number of the tcpd_container already
 * exists the item will be discarded.
 * 
 * @param storage Buffer structure to store data into.
 * @param item The data to store.
 * @param length The size of the data being stored in bytes.
 */

void put(struct buffer* storage, tcpd_container *item, size_t length) {
    int index;
    if (pthread_mutex_lock(&storage->mutex))
        perror(NULL);
    for (index = storage->tail; index != storage->head; index = (index+1) % BUFFER_SIZE) {
        if (storage->contents[index].data.seq == item->seq) {
            pthread_mutex_unlock(&storage->mutex);
            return;
        }
    }
    storage->contents[storage->head].length = length;
    gettimeofday(&storage->contents[storage->head].timestamp, NULL);
    storage->contents[storage->head].data = *item;
    storage->head = (storage->head + 1) % BUFFER_SIZE;
    if (pthread_mutex_unlock(&storage->mutex))
        perror(NULL);
}

/**
 * Removes the data with the specified sequence number from the buffer. If the
 * removal results in a gap in the data, storage items are moved to close it.
 * 
 * @param storage Buffer structure to remove the data from.
 * @param seq Sequence number of the item being removed.
 * @param item Pointer to where the message should be copied to.
 * @param length Pointer to where the length of the message should be copied to.
 * @return The timestamp of the item removed.
 */

struct timeval get(struct buffer* storage, uint32_t seq, tcpd_container *item, size_t *length) {
    int index;
    struct timeval result;
    *length = 0;
    if (pthread_mutex_lock(&storage->mutex))
        perror(NULL);
    for (index = storage->tail; index != storage->head; index = (index+1) % BUFFER_SIZE) {
        if (storage->contents[index].data.seq == seq) {
            int inner = storage->tail;
            struct node remove;
            while (inner != (index+1) % BUFFER_SIZE) {
                struct node temp = storage->contents[inner];
                storage->contents[inner] = remove;
                remove = temp;
                inner = (inner+1) % BUFFER_SIZE;
            }
            *item = remove.data;
            *length = remove.length;
            result = remove.timestamp;
            storage->tail = (storage->tail+1) % BUFFER_SIZE;
            break;
        }
    }
    if (pthread_mutex_unlock(&storage->mutex))
        perror(NULL);
    return result;
}

/**
 * Returns the smallest sequence number currently stored in the buffer. It
 * should be noted that buffer elements are not necessarily stored sequentially
 * by sequence number, so a linear search must examine all elements in the
 * buffer.
 * 
 * @param storage Buffer structure to search.
 * @return The lowest sequence number currently in the buffer, or UINT_MAX if
 *     no elements are currently being stored.
 */

uint32_t lowest_seq(struct buffer* storage) {
    uint32_t min = UINT_MAX;
    int index;
    if (pthread_mutex_lock(&storage->mutex))
        perror(NULL);
    for (index = storage->tail; index != storage->head; index = (index+1) % BUFFER_SIZE) {
        if (storage->contents[index].data.seq < min) {
            min = storage->contents[index].data.seq;
        }
    }
    if (pthread_mutex_unlock(&storage->mutex))
        perror(NULL);
    return min;
}
